# coding=utf-8

"""
将本地的修改通过ftp一键同步到服务器上 ，适合维护网站并且经常改动代码的情况(监测文件变动依赖于svn,git)
"""
import os,time,sys,inspect,getopt
import stat
import string
from ftplib import FTP
from ftplib import FTP_TLS as FTPS
import ConfigParser
import paramiko

#获取脚本所在的路径
def current_path():   
    path=os.path.realpath(sys.path[0])  
    if os.path.isfile(path):  
        path=os.path.dirname(path)  
        return os.path.abspath(path)  
    else:  
        caller_file=inspect.stack()[1][1]  
        return os.path.abspath(os.path.dirname(caller_file))

#日期转时间戳        
def datetime_timestamp(dt):
  s = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S'))
  return int(s)

#检查配置文件是否正确 
def checkini(config_file):
  try:
    ck_fp=open(config_file,'r')
    ck_fp.close()
  except:
    print u'配置文件config.ini不存在'
    sys.exit()
    
  ck_cf = ConfigParser.ConfigParser()
  try:
    ck_cf.read(config_file)
    sitenum=ck_cf.getint('site','sitenum')
    if sitenum<1:
      print u'参数[site]-sitenum必须是大于0的整数'
      sys.exit()
    sitenames=[]
    for siteindex in xrange(1,sitenum+1):
      sitenames.append(ck_cf.get('site',str(siteindex)))
    
    sections=ck_cf.sections()
    for sitename in sitenames:
      if 'remote_'+sitename not in sections:
         print u'配置文件中缺少[remote_'+sitename+u']段'
         sys.exit()
      if 'local_'+sitename not in sections:
         print u'配置文件中缺少[local_'+sitename+u']段'
         sys.exit()
      remote_mode = ck_cf.get('remote_'+sitename,'mode') 
      if remote_mode not in ['ftp','sftp']:
        print u'站点'+sitename+u':[local_'+sitename+u'][mode]参数不支持,当前支持ftp,sftp'
        sys.exit()
      remote_host =ck_cf.get('remote_'+sitename,'host')
      remote_user    =ck_cf.get('remote_'+sitename,'user')
      remote_webroot =ck_cf.get('remote_'+sitename,'webroot')
      remote_passwd  =ck_cf.get('remote_'+sitename,'passwd')
      remote_port    =ck_cf.get('remote_'+sitename,'port') 
      if remote_mode=='ftp':     
        remote_passive =ck_cf.getboolean('remote_'+sitename,'passive')
        remote_ssl     =ck_cf.getboolean('remote_'+sitename,'ssl') 
      local_webroot =ck_cf.get('local_'+sitename,'local_webroot')
      if not os.path.exists(local_webroot):
        print u'站点'+sitename+u':[local_'+sitename+u'][local_webroot]:'+local_webroot+u'目录不存在'
        sys.exit()
        
      version_control = ck_cf.get('local_'+sitename,'version_control')
      if version_control not in ['svn','git']:
        print u'站点'+sitename+u':[local_'+sitename+u'][version_control]参数不支持,当前支持svn,git'
        sys.exit()
        
      include_path = ck_cf.get('local_'+sitename,'include_path')
      if include_path!='':
        paths=string.split(include_path,',')
        for path in paths:
          if not os.path.exists(local_webroot+path):
            print u'站点'+sitename+u':[local_'+sitename+u'][include_path]中的目录'+path+u'不存在'
            sys.exit()
      
      exclude_path = ck_cf.get('local_'+sitename,'exclude_path').replace('\\','/')
      if exclude_path!='':
        paths=string.split(exclude_path,',')
        for path in paths:
          if not os.path.exists(local_webroot+path):
            print u'站点'+sitename+u':[local_'+sitename+u'][exclude_path]中的目录'+path+u'不存在'
            sys.exit()
      
      exclude_file = ck_cf.get('local_'+sitename,'exclude_file').replace('\\','/')
      if exclude_file!='':
        files=string.split(exclude_file,',')
        for exfile in files:
          if not os.path.exists(local_webroot+exfile):
            print u'站点'+sitename+u':[local_'+sitename+u'][exclude_file]中的文件'+exfile+u'不存在'
            sys.exit()
                      
      lasttime= ck_cf.get('local_'+sitename,'lasttime')
      try: 
        datetime_timestamp(lasttime)
      except:
        print u'站点'+sitename+u':[local_'+sitename+u'][lasttime]参数格式不正确,应为2014-01-09 00:00:00'
        sys.exit()      
  except Exception,e:
    print u'检查配置文件失败'
    print e
    sys.exit()
  
def  usage():
  print u"-s 参数为更新站点的名字 all-更新所有站点"
  print u"-l 列出所有站点"
  print u"-h help"
  
class Remote_Sync:
  def __init__(self,cf,sitename):
    self.sitename=sitename
    self.cf=cf
    self.remote_mode = self.cf.get('remote_'+self.sitename,'mode')
    self.remote_host =self.cf.get('remote_'+self.sitename,'host')
    self.remote_user    =self.cf.get('remote_'+self.sitename,'user')
    self.remote_webroot =self.cf.get('remote_'+self.sitename,'webroot')
    self.remote_passwd  =self.cf.get('remote_'+self.sitename,'passwd')
    self.local_webroot =self.cf.get('local_'+self.sitename,'local_webroot')
    self.remote_port    =self.cf.get('remote_'+self.sitename,'port')
    if self.remote_mode=='ftp':      
      self.remote_passive =self.cf.getboolean('remote_'+self.sitename,'passive')
      self.remote_ssl     =self.cf.getboolean('remote_'+self.sitename,'ssl')
      if self.remote_ssl:
        self.ftp = FTPS()
      else:
        self.ftp = FTP()
    self.version_control = self.cf.get('local_'+self.sitename,'version_control') 
    self.include_path = self.cf.get('local_'+self.sitename,'include_path')
    self.exclude_path = self.cf.get('local_'+self.sitename,'exclude_path').replace('\\','/')
    self.exclude_file = self.cf.get('local_'+self.sitename,'exclude_file').replace('\\','/')
    self.lasttime= self.cf.get('local_'+self.sitename,'lasttime') 
    self.errorinfo='success'  #错误信息
    self.lfiles=[]  #本地文件列表
    try:
      os.chdir(self.local_webroot)
    except:
      self.displaymsg(self.local_webroot+u'目录不存在')
      self.writeLogs(self.local_webroot+u'目录不存在')
      self.errorinfo='error'
      
  def __del__(self):
    if self.remote_mode=='ftp':
      self.ftp.close()
    elif self.remote_mode=='sftp':
      self.ssh.close()

  def writeLogs(self,msg):
    global logfile
    msg=time.strftime('%Y-%m-%d %H:%M:%S')+'  '+u'[站点'+self.sitename+'] '+unicode(msg)+'\n'
    f=open(logfile,'a+')
    f.write(msg.encode('utf8'))
    f.close()
    
  def displaymsg(self,msg): 
    print u'站点'+self.sitename+' '+unicode(msg)
    
  def ftpconnect(self):
    if self.errorinfo!='success':
      return
    #初始化 FTP 链接
    if self.remote_ssl:          
        self.displaymsg(u'ftp ssl connect '+self.remote_host+u':'+str(self.remote_port))
        try:   
          self.ftp.set_pasv(self.remote_passive)
          self.ftp.connect(self.remote_host,self.remote_port)
          self.ftp.login(self.remote_user,self.remote_passwd)
          self.ftp.prot_p()
        except Exception,e:#可能服务器不支持ssl,或者用户名密码不正确
          self.displaymsg(e)
          self.displaymsg(' Make sure the SSL is on ; FTP parameter is OK')
          self.writeLogs(u'ftp ssl connect '+self.remote_host+u':'+str(self.remote_port)+u' failed '+unicode(e))
          self.errorinfo='error'
          return       
    else:
        self.displaymsg(u'ftp connect '+self.remote_host+u':'+str(self.remote_port))
        try:
          self.ftp.set_pasv(self.remote_passive)
          self.ftp.connect(self.remote_host,self.remote_port)
          self.ftp.login(self.remote_user,self.remote_passwd)
        except Exception,e:
          self.displaymsg(e)
          self.displaymsg('Make sure FTP parameter is OK')
          self.writeLogs(u'ftp connect '+self.remote_host+u':'+str(self.remote_port)+u' failed '+unicode(e))
          self.errorinfo='error'
          return       

    #print self.ftp.getwelcome()    
    self.writeLogs(u'ftp connect '+self.remote_host+u':'+str(self.remote_port)+u' success')
    try:
      self.ftp.cwd(self.remote_webroot)    
      self.displaymsg(u'ftp current path :'+self.ftp.pwd())
    except Exception,e:
      self.displaymsg(e)
      self.writeLogs(u'change remote_webroot '+self.remote_webroot+u' failed '+unicode(e))
      self.errorinfo='error'
      return       
  
  #sftp连接函数
  def sftpconnect(self):
    if self.errorinfo!='success':
      return
    self.displaymsg(u'sftp connect '+self.remote_host+u':'+str(self.remote_port))
    try:
      self.ssh = paramiko.Transport((self.remote_host,int(self.remote_port)))
      self.ssh.connect(username = self.remote_user, password = self.remote_passwd)
      self.sftp = paramiko.SFTPClient.from_transport(self.ssh)
    except Exception,e:
      self.displaymsg(e)
      self.displaymsg('Make sure SFTP parameter is OK')
      self.writeLogs(u'sftp connect '+self.remote_host+u':'+str(self.remote_port)+u' failed '+unicode(e))
      self.errorinfo='error'
      return
    
    try:
      self.sftp.chdir(self.remote_webroot)
      self.displaymsg(u'sftp current path :'+self.sftp.getcwd())
    except Exception,e:
      self.displaymsg(e)
      self.writeLogs(u'change remote_webroot '+self.remote_webroot+u' failed '+unicode(e))
      self.errorinfo='error'
      return         
      
  #更新最后上传时间
  def setLastTime(self):
    global config_file
    self.cf.set("local_"+self.sitename, "lasttime", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
    self.cf.write(open(config_file, "w"))
    
  def getSvnFiles(self): 
    if self.errorinfo!='success':
      return
    print u'更新SVN'
    os.system('svn up')  #svn获取列表前需要同步服务器才能取到最新的列表     
    try:
      file_list=os.popen('svn list -R','r')
    except:
      self.displaymsg(u':'+self.local_webroot+u' svn命令执行失败')
      self.writeLogs(u':'+self.local_webroot+u' svn命令执行失败')
      self.errorinfo='error'
      return
      
    for line in file_list:
      line=line.strip()
      self.lfiles.append(line)
    
    if len(self.lfiles)==0:
      self.displaymsg(u'svn获取文件失败')
      self.writeLogs(u'svn获取文件失败')
      self.errorinfo='error'
      
  def getGitFiles(self): 
    if self.errorinfo!='success':
      return 
    print u'更新GIT'    
    try:
      file_list=os.popen('git ls-files','r')
    except:
      self.displaymsg(u':'+self.local_webroot+u' git命令执行失败')
      self.writeLogs(u':'+self.local_webroot+u' git命令执行失败')
      self.errorinfo='error'
      return
      
    for line in file_list:
      line=line.strip()
      self.lfiles.append(line)
    
    if len(self.lfiles)==0:
      self.displaymsg(u'git获取文件失败')
      self.writeLogs(u'git获取文件失败')
      self.errorinfo='error'

  def walk_path(self,top):
    """ 
    Args:
        top: 相对web根目录的相对路径 
    """
    flist=[]
    for root, dirs, files in os.walk(top, topdown=False):
        for name in files:
            f=os.path.join(root, name).replace('\\','/')[len(self.local_webroot):]
            flist.append(f)
    return flist        

  #获取不依赖[版本控制]监测变动的文件列表
  def getKcFiles(self):
    if self.errorinfo!='success':
      return   
    if self.include_path!='':
      paths=string.split(self.include_path,',')
      for path in paths:
        self.lfiles.extend(self.walk_path(os.path.join(self.local_webroot,path)))
        
  def uploadfile(self):
    if self.errorinfo!='success':
      return
    uploadNum=0 #上传文件数量 
    successNum=0 #成功数量
    failNum=0 #失败数量
    lastTime=datetime_timestamp(self.lasttime)
   
    self.writeLogs(u'开始上传文件')
    for line in self.lfiles:
        filename=line.strip()
        
        #排除目录
        if self.exclude_path!='':
          exclude_paths= string.split(self.exclude_path,',')
          findpath=False
          for expath in exclude_paths:
            if expath+'/'==filename[0:len(expath)+1]:  #加斜杠防止不完全匹配
              self.displaymsg(u'排除目录'+expath+u'中的文件'+filename)
              self.writeLogs(u'排除目录'+expath+u'中的文件'+filename)
              findpath=True
              break
          if findpath:
            continue
            
        #排除文件
        if self.exclude_file!='':
          exclude_files= string.split(self.exclude_file,',')
          findfile=False
          for exfile in exclude_files:
            if exfile==filename:
              self.displaymsg(u'排除文件'+exfile)
              self.writeLogs(u'排除文件'+exfile)
              findfile=True
              break
          if findfile:
            continue
                             
        fullname=self.local_webroot+filename
        if os.path.isfile(fullname):
            _st=os.stat(fullname)
            st_mtime = _st[stat.ST_MTIME]        
            if st_mtime > lastTime:#如果从上次上传后，文件修改过     
              uploadNum=uploadNum+1
              self.displaymsg(u'上传文件'+fullname)
              self.writeLogs(u'上传文件'+fullname)
              if self.remote_mode=='ftp':
                 failNum,successNum=self.ftpuploadfile(fullname,filename,failNum,successNum)
              elif self.remote_mode=='sftp':
                 failNum,successNum=self.sftpuploadfile(fullname,filename,failNum,successNum)                
    
    if  uploadNum >0:
      self.displaymsg(u'共上传'+str(uploadNum)+u'个文件,成功'+str(successNum)+u'个文件,失败'+str(failNum)+u'个文件')
      self.writeLogs(u'共上传'+str(uploadNum)+u'个文件,成功'+str(successNum)+u'个文件,失败'+str(failNum)+u'个文件')
    else:
      self.displaymsg(u'没有上传文件')
      self.writeLogs(u'没有上传文件')
    if failNum==0:
      self.setLastTime()
    if self.remote_mode=='ftp':
      self.ftp.quit() 
    elif self.remote_mode=='sftp':
      self.ssh.close()
  
  #ftp上传文件  
  def ftpuploadfile(self,fullname,filename,failNum,successNum):
    bufsize=1024
    file_handler = open(fullname,'rb')
    try:
        remote_file=self.remote_webroot+filename
        self.ftp.storbinary('STOR '+remote_file,file_handler,bufsize) 
    except Exception,e:
        try:
            ftpdirs=os.path.dirname(filename).split('/')
            for ftpdir in ftpdirs:
              try:
                self.ftp.mkd(ftpdir)
              except:
                pass #忽略创建目录的错误
              self.ftp.cwd(ftpdir)                               
            self.ftp.cwd(self.remote_webroot)
            self.ftp.storbinary('STOR '+remote_file,file_handler,bufsize) 
        except Exception,e:
            self.displaymsg(u'上传文件'+fullname+u'失败 '+unicode(e))
            self.writeLogs(u'上传文件'+fullname+u'失败 '+unicode(e))
            failNum=failNum+1
            return failNum,successNum
    finally:        
        file_handler.close()
    successNum=successNum+1
    return failNum,successNum
         
  
  #sftp上传文件  
  def sftpuploadfile(self,fullname,filename,failNum,successNum):
    remote_file=self.remote_webroot+filename
    local_file=fullname
    try:
      self.sftp.put(local_file, remote_file)
    except Exception,e:
      try:
        ftpdirs=os.path.dirname(filename).split('/')
        for ftpdir in ftpdirs:
          try:
            self.sftp.mkdir(ftpdir)
          except:
            pass #忽略创建目录的错误
          self.sftp.chdir(ftpdir)                               
        self.sftp.chdir(self.remote_webroot)
        self.sftp.put(local_file, remote_file) 
      except Exception,e:
        self.displaymsg(u'上传文件'+fullname+u'失败 '+unicode(e))
        self.writeLogs(u'上传文件'+fullname+u'失败 '+unicode(e))
        failNum=failNum+1
        return failNum,successNum      
    successNum=successNum+1  
    return failNum,successNum   
    
  def sync_web(self):
    #查找本地文件
    if self.version_control=='svn':
      self.getSvnFiles()
    elif self.version_control=='git':
      self.getGitFiles()
    self.getKcFiles()
    #print self.lfiles
    if self.remote_mode=='ftp':
      #连接ftp
      self.ftpconnect()
    elif self.remote_mode=='sftp':
      #连接ftp
      self.sftpconnect()
    #上传文件
    self.uploadfile()
    

if __name__=='__main__':    
  if len(sys.argv)<=1:
    usage()
    sys.exit()
  
  site=''
  listsite=False  #是否列站点
  
  try:
    opts, args = getopt.getopt(sys.argv[1:], "hls:")
  except:
    usage()
    sys.exit()
         
  for op, value in opts:
      if op == "-h":
         usage()
         sys.exit()
      elif op == "-l":
         listsite=True
      elif op== "-s":
         site=value
      else:
         usage()
         sys.exit()
  
  script_path=current_path()
  riqi = time.strftime('%Y%m%d%H%M%S', time.localtime())
  log_path = os.path.join(script_path, 'log')
  if not os.path.isdir(log_path):
    os.makedirs(log_path)
  logfile=os.path.join(script_path,'log',riqi+'_'+site+'.log')
  
  config_file= os.path.join(script_path,'config.ini') 
  
  checkini(config_file)
  
  cf=ConfigParser.ConfigParser()
  cf.read(config_file)
  sitenum=cf.getint('site','sitenum')
  sitenames=[]
  for siteindex in xrange(1,sitenum+1):
    sitenames.append(cf.get('site',str(siteindex)))
  
  #列出站点
  if listsite:
    for sitename in sitenames:
      print sitename
    sys.exit()
    
  if site=='':
    usage()
    sys.exit()
  
  if site!='all':
    if site not in sitenames:
      print u'站点'+site+u'不存在'
      sys.exit()
  
  for sitename in sitenames:
    if site=='all':
      print u'同步站点'+sitename
      sync=Remote_Sync(cf,sitename)
      sync.sync_web()
      del sync   
    else:
      if site==sitename:
        print u'同步站点'+sitename
        sync=Remote_Sync(cf,sitename)
        sync.sync_web() 




